use std::error::Error;
use std::fmt::format;
use serde::{Serialize,Deserialize};
use crate::lincni::error::Errors;
use crate::lincni::plugin::PluginInfo;

#[derive(Clone,Default,Debug)]
pub struct ConfigDecoder();

impl ConfigDecoder {
    pub fn decode(&self,data:Vec<u8>) -> Result<String,Box<dyn Error>> {
        let json:Conf = serde_json::from_slice(data.as_slice())?;
        if json.cni_version.is_empty() {
            return Ok(String::from("0.1.0"));
        }
        return Ok(json.cni_version);
    }

    pub fn greater_than_or_equal_to(&self,version:&str,other_version:&str) -> Result<bool,Box<dyn Error>> {
        let (firstMajor, firstMinor, firstMicro) = self.parse_version(version)?;
        let (secondMajor, secondMinor, secondMicro) = self.parse_version(other_version)?;

        if firstMajor > secondMajor {
            return Ok(true);
        } else if firstMajor == secondMajor {
            if firstMinor > secondMinor {
                return Ok(true);
            } else if firstMinor == secondMinor && firstMicro >= secondMicro {
                return Ok(true);
            }
        }
        return Ok(false);
    }

    pub fn parse_version(&self,version:&str) -> Result<(i32,i32,i32),Box<dyn Error>> {
        if version.is_empty() {
            return Ok((0,1,0));
        }
        let parts:Vec<&str> = version.split(".").collect();
        if parts.len() >= 4 {
            return Err(Errors::FromStr(format!("invalid version {}:too many parts",version)).to_dyn());
        }

        let major = parts[0].parse::<i32>()?;
        let mut minor = 0i32;
        let mut micro = 0i32;
        if parts.len() >= 2 {
            minor = parts[1].parse()?;
        }
        if parts.len() >= 3 {
            micro = parts[2].parse()?;
        }

        Ok((major, minor, micro))
    }
}

#[derive(Serialize,Deserialize,Clone,Debug,Default)]
struct Conf {
    #[serde(rename="cniVersion")]
    pub cni_version:String,
}

#[derive(Clone,Default,Debug)]
pub struct Reconciler();

impl Reconciler {
    pub fn check(&self,cv:String,pi:PluginInfo) -> Result<(),ErrorIncompatible>{
        for i in pi.support_versions() {
            if cv == i {
                return Ok(());
            }
        }

        Err(ErrorIncompatible{
            config:cv,
            supported:pi.support_versions(),
        })
    }
}

pub struct ErrorIncompatible {
    pub config:String,
    pub supported: Vec<String>,
}

impl ErrorIncompatible {
    pub fn details(&self) -> String {
        format!("config is {},plugin support {:?}",self.config,self.supported)
    }

    pub fn error(&self) -> String {
        format!("incompatible CNI versions: {}",self.details())
    }
}


#[cfg(test)]
mod test {
    use std::error::Error;
    use crate::lincni::version::ConfigDecoder;

    #[test]
    fn test () {
        let str = r#"
        {
          "name": "cbr0",
          "cniVersion": "0.3.1",
          "plugins": [
            {
              "type": "flannel",
              "delegate": {
                "hairpinMode": true,
                "isDefaultGateway": true
              }
            },
            {
              "type": "portmap",
              "capabilities": {
                "portMappings": true
              }
            }
          ]
        }"#;
        let res=  ConfigDecoder::default().decode(str.as_bytes().to_vec());
        match res {
            Ok(s) => {println!("{}",s)}
            Err(e) => {println!("{}",e.to_string())}
        }
    }
}